﻿using System;
using System.Net;
using Microsoft.Ccr.Core;
using Bittorrent.Agents;
using Bittorrent.Bencode.Collections;
using Bittorrent.Bencode;
using Bittorrent.Extentions;
using Bittorrent.Ccr.Adapters.IO;

namespace Bittorrent.Client.Tracker {
    internal class Downloaded {
        public long Amount { get; set; }
    }
 
    internal class Uploaded {
        public long Amount { get; set; }
    }

    internal class HttpTrackerAgent : AgentBase {
        private readonly Port<HttpTrackerResponse> _responsePort = new Port<HttpTrackerResponse>();
        private readonly Port<HttpTrackerRequest> _announcePort = new Port<HttpTrackerRequest>();
        private readonly Port<Uploaded> _uploadedPort = new Port<Uploaded>();
        private readonly Port<Downloaded> _downloadedPort = new Port<Downloaded>();
        private readonly Port<Exception> _errorPort;
        private readonly Port<IPeer> _peerPort;
        private readonly HttpTrackerRequest _state;

        public HttpTrackerAgent(HttpTrackerRequest request, DispatcherQueue dispatcherQueue, Port<IPeer> peerPort, Port<Exception> errorPort)
            : base(dispatcherQueue) {
            if (request == null)
                errorPort.Post(new ArgumentNullException("request"));
            
            _errorPort = errorPort;
            _state = request;
            _peerPort = peerPort;

            Port = new PortSet<Downloaded,Uploaded>();

            Activate(
                Arbiter.Interleave(
                // ---------------------------------------------------------------------------------------
                    new TeardownReceiverGroup(Cleanup(s => {
                        _state.RequestEvent = HttpTrackerRequestType.Stopped;
                        Announce();
                    })),
                // ---------------------------------------------------------------------------------------
                    new ExclusiveReceiverGroup(Arbiter.Receive(true, _announcePort, r => DoAnnounce()),
                                               Arbiter.Receive(true, _downloadedPort, DownloadedHandler),
                                               Arbiter.Receive(true, _uploadedPort, u => _state.Uploaded += u.Amount),
                                               Arbiter.Receive(true, _responsePort, ResponseHandler)),
                // ---------------------------------------------------------------------------------------
                    new ConcurrentReceiverGroup()
                    ));
        }

        public PortSet<Downloaded, Uploaded> Port { get; set; }

        public void Announce() {
            _announcePort.Post(_state);
        }

        private void DoAnnounce() {
            var result = WebRequestAdapter.ReadResponse(WebRequest.Create(_state.GetRequestUri()), TaskQueue);

            Activate(Arbiter.Choice(result, data => {
                var response = new HttpTrackerResponse((Dictionary)BencodeFactory.Decode(data));
                _responsePort.Post(response);
            },
                                    e => _errorPort.Post(e)));
        }

        private void ResponseHandler(HttpTrackerResponse response) {
            // If the RequestType was stopped we don't need to contine.
            if (_state.RequestEvent == HttpTrackerRequestType.Stopped) {
                // The only message acceped after a stop is a start so we can prepare for that
                _state.RequestEvent = HttpTrackerRequestType.Started;
                return;
            }
            else {
                // Completed, Stopped, and Started only needs to be sent once
                // so we can reset state to Non
                _state.RequestEvent = HttpTrackerRequestType.Non;
            }

            // Todo: Handle Complete and Incomplete

            _peerPort.PostMany(response.Peers);

            Port<DateTime> timePort;
            if (response.Interval == -1) {
                // Revisit: The default TimeSpan value should probebly be a config parameter.
                timePort = TimeoutPort(new TimeSpan(0, 0, 30, 0));
            }
            else {
                timePort = TimeoutPort(new TimeSpan(0, 0, 0, (int)response.Interval));
            }

            Activate(Arbiter.Receive(false, timePort, d => Announce()));
        }

        private void DownloadedHandler(Downloaded d) {
            _state.Downloaded += d.Amount;
            _state.Left -= d.Amount;
            if (_state.Left == 0) {
                _state.RequestEvent = HttpTrackerRequestType.Completed;
                Announce();
            }
        }
    }
}
